from datetime import date
from typing import Iterable
from django.contrib.contenttypes.models import ContentType
from django.contrib.contenttypes.fields import GenericForeignKey, GenericRelation
from django.db import models
from django.db.models import Q, When, Case, Value
from django.contrib.auth.models import User
from django.core.validators import MinValueValidator, MaxValueValidator
from django.urls import reverse
from django.utils import timezone


# Restaurant
# User
# Rating
class Restaurant(models.Model):
    class TypeChoices(models.TextChoices):
        INDIAN = 'IN', 'Indian'
        CHINESE = 'CH', 'Chinese'
        ITALIAN = 'IT', 'Italian'
        GREEK = 'GR', 'Greek'
        MEXICAN = 'MX', 'Mexican'
        FASTFOOD = 'FF', 'Fast Food'
        OTHER = 'OT', 'Other'

    name = models.CharField(max_length=100)
    website = models.URLField(default='')
    date_opened = models.DateField()
    latitude = models.FloatField()
    longitude = models.FloatField()
    restaurant_type = models.CharField(
        choices=TypeChoices.choices, max_length=2)
    capacity = models.PositiveSmallIntegerField(null=True, blank=True)
    nickname = models.CharField(max_length=200, default='')
    comments = GenericRelation('Comment', related_query_name='restaurant')

    @property
    def restaurant_name(self):
        return self.nickname or self.name

    @property
    def was_opened_this_year(self):
        current_year = timezone.now().year
        return self.date_opened.year == current_year

    def is_opend_after(self, date: date) -> bool:
        return self.date_opened > date

    def get_absolute_url(self):
        return reverse('restaurant-detail', kwargs={'pk': self.pk})

    class Meta:
        get_latest_by = ['date_opened']

    def __str__(self) -> str:
        return f'{self.name}'


class Staff(models.Model):
    name = models.CharField(max_length=128)
    restaurants = models.ManyToManyField(
        Restaurant, related_name='staffs', through='StaffRestaurant')

    class Meta:
        verbose_name = 'Staff'
        verbose_name_plural = 'Staff'

    def __str__(self) -> str:
        return f'{self.name}'


class StaffRestaurant(models.Model):
    staff = models.ForeignKey(
        Staff, on_delete=models.CASCADE, db_constraint=False, related_name='staff_restaurants',
    )
    restaurant = models.ForeignKey(
        Restaurant, on_delete=models.CASCADE, db_constraint=False, related_name='staff_restaurants',
    )
    salary = models.FloatField(null=True, default=None)


class Rating(models.Model):
    user = models.ForeignKey(
        User, on_delete=models.CASCADE, related_name='ratings', db_constraint=False
    )
    restaurant = models.ForeignKey(
        Restaurant, on_delete=models.CASCADE, related_name='ratings', db_constraint=False
    )
    rating = models.PositiveSmallIntegerField(
        validators=[MinValueValidator(1), MaxValueValidator(5)]
    )
    comments = GenericRelation('Comment', related_query_name='rating')

    def __str__(self) -> str:
        return f'Rating: {self.rating}'

    class Meta:
        constraints = [
            models.CheckConstraint(
                name='rating_value_valid',
                check=Q(rating__gte=1, rating__lte=5),
                violation_error_message='Rating must be between 1 and 5',
            ),
            models.UniqueConstraint(
                fields=['user', 'restaurant'],
                name='unique_user_restaurant_pair',
                violation_error_message='A user can only rate a restaurant once',
            )
        ]


class Sale(models.Model):
    restaurant = models.ForeignKey(
        Restaurant, on_delete=models.SET_NULL, related_name='sales', db_constraint=False, null=True,
    )
    income = models.DecimalField(max_digits=8, decimal_places=2)
    expenditure = models.DecimalField(max_digits=8, decimal_places=2)
    datetime = models.DateTimeField()

    profit = models.GeneratedField(
        expression=models.F('income') - models.F('expenditure'),
        output_field=models.DecimalField(max_digits=8, decimal_places=2),
        db_persist=True,
    )

    suggested_tip = models.GeneratedField(
        expression=Case(
            When(income__gte=10, then=models.F('income') * 0.2),
            default=Value(0),
            output_field=models.DecimalField(max_digits=8, decimal_places=2),
        ),
        output_field=models.DecimalField(max_digits=8, decimal_places=2),
        db_persist=True,
    )


class Product(models.Model):
    name = models.CharField(max_length=100)
    number_in_stock = models.PositiveSmallIntegerField()

    def __str__(self):
        return self.name


class Order(models.Model):
    product = models.ForeignKey(
        Product, on_delete=models.CASCADE, related_name='orders', db_constraint=False)
    number_of_items = models.PositiveSmallIntegerField()

    def __str__(self):
        return f'{self.number_of_items} x {self.product.name}'


class Comment(models.Model):
    text = models.TextField()
    content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE, related_name='comments',
                                     db_constraint=False)
    object_id = models.PositiveSmallIntegerField()
    content_object = GenericForeignKey('content_type', 'object_id')


class TaskStatus(models.IntegerChoices):
    TODO = 1
    IN_PROGRESS = 2
    COMPLETED = 3


class Task(models.Model):
    name = models.CharField(max_length=100)
    created_at = models.DateTimeField(auto_now_add=True)
    status = models.IntegerField(choices=TaskStatus.choices)

    def __str__(self) -> str:
        return self.name


class InProgressTask(Task):

    class Meta:
        proxy = True

    class Manager(models.Manager):
        def get_queryset(self):
            return super().get_queryset().filter(status=TaskStatus.IN_PROGRESS)

    objects = Manager()

    def save(self, *args, **kwargs) -> None:
        if self._state.adding:
            self.status = TaskStatus.IN_PROGRESS
        super().save(*args, **kwargs)

    def __str__(self) -> str:
        return f'{self.name} in progress'


class Event(models.Model):
    name = models.CharField(max_length=100)
    start_date = models.DateTimeField()
    end_date = models.DateTimeField()

    duration = models.GeneratedField(
        expression=models.F('end_date') - models.F('start_date'),
        output_field=models.DurationField(),
        db_persist=True
    )
